/**
 * 
 */
package com.googlecode.xcache4j.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.transcoders.Transcoder;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import com.googlecode.xcache4j.CacheClient;
import com.googlecode.xcache4j.Holder;
import com.googlecode.xcache4j.transcoder.HessianSerializingTranscoder;

/**
 * @author xy.du
 * 2015年1月12日
 */
public final class XmemcacheImpl implements CacheClient{

	static final ConcurrentHashMap<Class<?>, Transcoder<?>> transcoders = new ConcurrentHashMap<Class<?>, Transcoder<?>>();
	static final Logger logger = LoggerFactory.getLogger(XmemcacheImpl.class);
	private MemcachedClient memcachedClient;
	private Transcoder<Object> defaultTranscoder;
	static final int DEFAULT_EXPER_TIME = 3600 * 24 * 29;
	static final int MTX_EXPER_TIME = 60;
	
	public XmemcacheImpl(MemcachedClient memcachedClient) {
		 Assert.notNull(memcachedClient, "memcachedClient cannot null.");
		 this.memcachedClient = memcachedClient;
		 defaultTranscoder = new HessianSerializingTranscoder();
	}
	
	public boolean add(String key,int exp,Object value)throws Exception{
		if(exp <= 0){
			exp = 60;
		}
		return memcachedClient.add(key, exp, value);
	}
	
	public <T> T get(String key) throws Exception{
		return memcachedClient.get(key);
	}
	
	/**
	 * loadDB is true then load from db,false: get cache
	 * 
	 */
	public <T> T get(String key,Callable<? extends T> callable,final boolean loadDB) throws Exception{
		return get(key, DEFAULT_EXPER_TIME, callable, loadDB);
	}
	
	public <T> T get(String key,int exper,Callable<? extends T> callable,final boolean loadDB) throws Exception{
		T value = null;
		if(loadDB){
			return loadAndSet(key, exper, callable,null);
		}
		value = memcachedClient.get(key);
		if( value == null ){
			value = loadAndSet(key,exper, callable, null);
		}
		return value;
	}
	/**
	 * @ callable 
	 * @ loadDB is true then load from db,false: get cache
	 *  if key not exist, add key first, then execute callable and set key  
	 */
	public <T> T getx(String key, Callable<? extends T> callable, boolean loadDB) throws Exception {
		T value = null;
		if(loadDB){
			return loadAndSet(key, DEFAULT_EXPER_TIME, callable,null);
		}
		value = memcachedClient.get(key);
		if( value == null ){
			String mtx_key = key+"_mtx";
			boolean flag = memcachedClient.add(mtx_key, MTX_EXPER_TIME, 1);
			if(flag){
				try {
					value = loadAndSet(key,DEFAULT_EXPER_TIME, callable, null);
				} catch (Exception e) {
					logger.error("",e);
				}finally{
					memcachedClient.deleteWithNoReply(mtx_key);	
				}
			}else{
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (Exception e) {}
				value = memcachedClient.get(key);
			}
		}
		return value;
	}
	
	public <T> Map<String,T> getMult(String... keys) throws Exception{
		List<String> listKey = new ArrayList<String>();
		for(String key : keys){
			if(StringUtils.isNotBlank(key)){
				listKey.add(key);
			}
		}
		return memcachedClient.get(listKey);
	}
	
   @SuppressWarnings("unchecked")
   public <T> Map<String,T> hgetMult(String... keys) throws Exception{
		List<String> listKey = new ArrayList<String>();
		for(String key : keys){
			if(StringUtils.isNotBlank(key)){
				listKey.add(key);
			}
		}
		return (Map<String,T>)memcachedClient.get(listKey, defaultTranscoder);
	}
	/**
	 * get value with hessian transcoder 
	 * 
	 */
	public <T> T hget(String key,Callable<? extends T> callable,final boolean loadDB) throws Exception{
		T value = null;
		if(loadDB){
			return loadAndSet(key, DEFAULT_EXPER_TIME, callable,defaultTranscoder);
		}
		value = memcachedClient.get(key);
		if( value == null ){
			value = loadAndSet(key, DEFAULT_EXPER_TIME, callable,defaultTranscoder);
		}
		return value;
	}
	
	/**
	 * get value with hessian transcoder,if notexist , add key_mtx first then set key
	 * 
	 */
	public <T> T hgetx(String key,Callable<? extends T> callable,final boolean loadDB) throws Exception{
		T value = null;
		if(loadDB){
			return loadAndSet(key, DEFAULT_EXPER_TIME, callable,defaultTranscoder);
		}
		value = memcachedClient.get(key);
		if( value == null ){
			String mtx_key = key+"_mtx";
			boolean flag = memcachedClient.add(mtx_key, MTX_EXPER_TIME, 1);
			if(flag){
				try {
					value = loadAndSet(key,DEFAULT_EXPER_TIME, callable, defaultTranscoder);
				} catch (Exception e) {
					logger.error("",e);
				}finally{
					memcachedClient.deleteWithNoReply(mtx_key);	
				}
			}else{
				try {
					TimeUnit.SECONDS.sleep(3);
				} catch (Exception e) {}
				value = memcachedClient.get(key);
			}
		}
		return value;
	}
	
	public <T> boolean set(String key, T value) {
		return set(key, value, DEFAULT_EXPER_TIME);
	}

	public <T> boolean set(String key,T value,int exper){
		try {
			Holder holder = new Holder(System.currentTimeMillis(),exper,value);
			return memcachedClient.set(key, exper, holder);
		} catch (Exception e) {
			logger.error("set value to cache error", e);
		}
		return false;
	}
	
	public <T> boolean setWithNoReply(String key,T value){
		try {
			memcachedClient.setWithNoReply(key, DEFAULT_EXPER_TIME, value);
			return true;
		} catch (Exception e) {
			logger.error("set value to cache error", e);
		}
		return false;
	}
	
	public <T> boolean setWithNoReply(String key,T value,int exper){
		try {
			memcachedClient.setWithNoReply(key, exper, value);
			return true;
		} catch (Exception e) {
			logger.error("set value to cache error", e);
		}
		return false;
	}
	
	public <T> boolean hset(String key,T value){
		return set(key, value, DEFAULT_EXPER_TIME);
	}
	
	public <T> boolean hset(String key,T value,int exper){
		try {
			memcachedClient.setWithNoReply(key, DEFAULT_EXPER_TIME, value, defaultTranscoder);
			return true;
		} catch (Exception e) {
			logger.error("set value to cache error", e);
		}
		return false;
	}
	
	private <T> T loadAndSet(String key,int exper,Callable<? extends T> callable,Transcoder<Object> coder)throws Exception{
		Assert.notNull(callable, "callable argument cannot null");
		T val = callable.call();
		if(val != null){
			if(coder != null){
				memcachedClient.setWithNoReply(key, exper, val, coder);
			}else{
				memcachedClient.setWithNoReply(key, exper, val);
			}
		} 
		return val;
	}
 
	public boolean delete(String key){
		try {
			memcachedClient.deleteWithNoReply(key);
			return true;
		} catch (Exception e) {
			logger.error("delete key error, key="+key, e);
		}
		return false;
	}
	
}
